#include  "pcb.h"
#include  "doxon.h"
#include  "schedule.h"
#include  "queue.h"
#include  "semaphor.h"
#include  "kernsem.h"
#include  <dos.h>
#include  <stdlib.h>

const int PCB::NEW      = 0;
const int PCB::READY    = 1;
const int PCB::BLOCKED  = 2;
const int PCB::FINISHED = 3;

int       PCB::id_cnt     = 0;
int       PCB::last_index = 0;

PCB* PCB::all_pcbs[MAX_PROC + 1]; 


PCB::~PCB() {
    lock();
    delete waitQueue;
    delete p_sp;
    unlock();
}

Thread* PCB::getThreadById(ID id) {
    int i;
    Thread* tmp;
    lock();

    i = 0;
    while ( i < PCB::last_index ) {
        if ( all_pcbs[i]->id == id )
            break;
        i++;
    }
    if ( i == PCB::last_index ) {
        /* Pre izlaska se mora uraditi unlock, inace ce sistem pasti */
        unlock();
        return NULL;
    }

    tmp = all_pcbs[i]->my_thread;

    unlock();
    return tmp;
}

PCB::PCB (Thread* myThread
          , UID _uid
          , Priority  priority 
          , StackSize stackSize 
          , Time      timeSlice) {
    lock();

    my_thread  = myThread;
    uid        = get_uniq_uid(_uid);
    id         = uid;
    stack_size = (stackSize > MAX_STACK)? MAX_STACK: stackSize;
    time_slice = timeSlice;
    waitQueue  = new Queue();
    state      = NEW;

    wait_return  = 0;
    time_wait    = (Time)0;
    kern_sem_own = NULL;

    add_to_list(this);

    unlock();
}

UID PCB::get_uniq_uid(UID _uid) {
    int i;
    int exist_dup;

    lock();
#define DUPLICATE_EXIST 1
#define GENERATED_DUP   2
    /* 1 - Ako generisemo duplikat */
    exist_dup = DUPLICATE_EXIST;

    while ( exist_dup ) {
        /* Generator */
        if ( _uid == -1 ) {
            _uid = id_cnt++;
        }

        for (i = 0; i < PCB::last_index; i++) {

            if ( all_pcbs[i]->uid == _uid ) {
                /* signalizacija za if posle "for" petlje */
                exist_dup = GENERATED_DUP;
                _uid      = -1;
                break;
            }
        }

        if (exist_dup == GENERATED_DUP)
            exist_dup = DUPLICATE_EXIST;
        else
            exist_dup = 0;
    }
    unlock();

    return _uid;
}

void PCB::add_to_list(PCB* pcb) {
    if ( pcb == NULL )
        return;
    lock();

    if ( last_index != MAX_PROC ) {
        all_pcbs[last_index] = pcb;
        last_index++;
    }

    unlock();
}

void PCB::del_from_list(PCB* pcb) {
    int i;

    lock();

    for (i = 0; i < last_index; i++) {
        if (all_pcbs[i] == pcb) {
#ifdef PCB_TEST
            delete all_pcbs[i];
#endif
            if ( i != (last_index - 1) ) {
                all_pcbs[i] = all_pcbs[last_index - 1];
            }
            last_index--;
            unlock();
            return;
        }
    }
    /* Ovaj unlock ce se izvrsiti ako brisemo nepostojeci PCB */
    unlock();
}

int PCB::exist_other_thr() {
    int last_index_tmp;
    lock();
    last_index_tmp = last_index;
    unlock();
    /* Ako u sistemu postoje samo dve sistemske niti to znaci da sistem moze da
     * se gasi */
    if ( last_index_tmp != 2 )
        return 1;
    else
        return 0;
}

void PCB::init_stack() {
    lock();
    /* Stack: pointer&seg. */
    static volatile unsigned tsp, tss;
    static volatile unsigned oldss, oldsp;

    static unsigned char     *tmpStack;
    /* Code: pointer&seg. */
    static volatile unsigned tip, tcs;

    p_sp = tmpStack = new unsigned char[stack_size];

    /* tmpStack + stack_size, pokazuje na kraj segmenta memorije. Posto stack u
     * x86 arch raste nadole */
    tsp = sp = FP_OFF(tmpStack + stack_size);
    tss = ss = FP_SEG(tmpStack + stack_size);

    tip = FP_OFF(&(Doxon::wrapper));
    tcs = FP_SEG(&(Doxon::wrapper));
    asm {
        mov oldss, ss;
        mov oldsp, sp;
        mov ss, tss;
        mov sp, tsp;

        pushf;
        pop ax;

        /* 
         * PUSH
         * flags, CS, IP, bp(0), 8xrandom
         */
        /* Setuje I bit u FLAGS registru */
        or ax, 0200h;
        push ax;

        /* Namesta mesto povratka posle prekidne rutine */
        mov ax, tcs;
        push ax;
        mov ax, tip;
        push ax;

        /* Stavlja bp=0 i random vrednosti ostalih registara */
        mov ax, 0;
        push ax;
        push bx;
        push cx;
        push dx;
        push es;
        push ds;
        push si;
        push di;
        push bp;

        mov tsp, sp;
        mov sp, oldsp;
        mov ss, oldss;
    }

    sp = tsp;

    unlock();
}

void PCB::refresh_blocked() {
    int i;

    for (i = 0; i < last_index; i++) {
        if (all_pcbs[i]->state != BLOCKED || all_pcbs[i]->time_wait == 0)
            continue;
        if (all_pcbs[i]->time_wait < all_pcbs[i]->time_passed) {
            /* Budimo nit */
            all_pcbs[i]->time_wait   = (Time)0;
            all_pcbs[i]->time_passed = (Time)0;

            /* Cuva konzistentnu vrednost semafora */
            all_pcbs[i]->kern_sem_own->del( all_pcbs[i] );
            all_pcbs[i]->kern_sem_own = NULL;
            all_pcbs[i]->wait_return = 1;

            all_pcbs[i]->state = PCB::READY;
            Scheduler::put(all_pcbs[i]);
        }
        /* increment */
        all_pcbs[i]->time_passed += (Time)minTimeSlice();

    }
}
